// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
#define NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_

#include <stdint.h>

#include <list>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "net/base/cache_type.h"
#include "net/base/completion_callback.h"
#include "net/base/net_export.h"

#if defined(OS_ANDROID)
#include "base/android/application_status_listener.h"
#endif

namespace base {
class Pickle;
class PickleIterator;
}

namespace disk_cache {

class SimpleIndexDelegate;
class SimpleIndexFile;
struct SimpleIndexLoadResult;

class NET_EXPORT_PRIVATE EntryMetadata {
public:
    EntryMetadata();
    EntryMetadata(base::Time last_used_time, uint64_t entry_size);

    base::Time GetLastUsedTime() const;
    void SetLastUsedTime(const base::Time& last_used_time);

    uint64_t GetEntrySize() const;
    void SetEntrySize(uint64_t entry_size);

    // Serialize the data into the provided pickle.
    void Serialize(base::Pickle* pickle) const;
    bool Deserialize(base::PickleIterator* it);

    static base::TimeDelta GetLowerEpsilonForTimeComparisons()
    {
        return base::TimeDelta::FromSeconds(1);
    }
    static base::TimeDelta GetUpperEpsilonForTimeComparisons()
    {
        return base::TimeDelta();
    }

private:
    friend class SimpleIndexFileTest;

    // There are tens of thousands of instances of EntryMetadata in memory, so the
    // size of each entry matters.  Even when the values used to set these members
    // are originally calculated as >32-bit types, the actual necessary size for
    // each shouldn't exceed 32 bits, so we use 32-bit types here.
    uint32_t last_used_time_seconds_since_epoch_;
    int32_t entry_size_; // Storage size in bytes.
};
static_assert(sizeof(EntryMetadata) == 8, "incorrect metadata size");

// This class is not Thread-safe.
class NET_EXPORT_PRIVATE SimpleIndex
    : public base::SupportsWeakPtr<SimpleIndex> {
public:
    // Used in histograms. Please only add entries at the end.
    enum IndexInitMethod {
        INITIALIZE_METHOD_RECOVERED = 0,
        INITIALIZE_METHOD_LOADED = 1,
        INITIALIZE_METHOD_NEWCACHE = 2,
        INITIALIZE_METHOD_MAX = 3,
    };
    // Used in histograms. Please only add entries at the end.
    enum IndexWriteToDiskReason {
        INDEX_WRITE_REASON_SHUTDOWN = 0,
        INDEX_WRITE_REASON_STARTUP_MERGE = 1,
        INDEX_WRITE_REASON_IDLE = 2,
        INDEX_WRITE_REASON_ANDROID_STOPPED = 3,
        INDEX_WRITE_REASON_MAX = 4,
    };

    typedef std::vector<uint64_t> HashList;

    SimpleIndex(const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
        SimpleIndexDelegate* delegate,
        net::CacheType cache_type,
        std::unique_ptr<SimpleIndexFile> simple_index_file);

    virtual ~SimpleIndex();

    void Initialize(base::Time cache_mtime);

    void SetMaxSize(uint64_t max_bytes);
    uint64_t max_size() const { return max_size_; }

    void Insert(uint64_t entry_hash);
    void Remove(uint64_t entry_hash);

    // Check whether the index has the entry given the hash of its key.
    bool Has(uint64_t entry_hash) const;

    // Update the last used time of the entry with the given key and return true
    // iff the entry exist in the index.
    bool UseIfExists(uint64_t entry_hash);

    void WriteToDisk(IndexWriteToDiskReason reason);

    // Update the size (in bytes) of an entry, in the metadata stored in the
    // index. This should be the total disk-file size including all streams of the
    // entry.
    bool UpdateEntrySize(uint64_t entry_hash, int64_t entry_size);

    using EntrySet = std::unordered_map<uint64_t, EntryMetadata>;

    static void InsertInEntrySet(uint64_t entry_hash,
        const EntryMetadata& entry_metadata,
        EntrySet* entry_set);

    // Executes the |callback| when the index is ready. Allows multiple callbacks.
    int ExecuteWhenReady(const net::CompletionCallback& callback);

    // Returns entries from the index that have last accessed time matching the
    // range between |initial_time| and |end_time| where open intervals are
    // possible according to the definition given in |DoomEntriesBetween()| in the
    // disk cache backend interface.
    std::unique_ptr<HashList> GetEntriesBetween(const base::Time initial_time,
        const base::Time end_time);

    // Returns the list of all entries key hash.
    std::unique_ptr<HashList> GetAllHashes();

    // Returns number of indexed entries.
    int32_t GetEntryCount() const;

    // Returns the size of the entire cache in bytes. Can only be called after the
    // index has been initialized.
    uint64_t GetCacheSize() const;

    // Returns whether the index has been initialized yet.
    bool initialized() const { return initialized_; }

    IndexInitMethod init_method() const { return init_method_; }

private:
    friend class SimpleIndexTest;
    FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, IndexSizeCorrectOnMerge);
    FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWriteQueued);
    FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWriteExecuted);
    FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWritePostponed);

    void StartEvictionIfNeeded();
    void EvictionDone(int result);

    void PostponeWritingToDisk();

    void UpdateEntryIteratorSize(EntrySet::iterator* it, int64_t entry_size);

    // Must run on IO Thread.
    void MergeInitializingSet(std::unique_ptr<SimpleIndexLoadResult> load_result);

#if defined(OS_ANDROID)
    void OnApplicationStateChange(base::android::ApplicationState state);

    std::unique_ptr<base::android::ApplicationStatusListener>
        app_status_listener_;
#endif

    // The owner of |this| must ensure the |delegate_| outlives |this|.
    SimpleIndexDelegate* delegate_;

    EntrySet entries_set_;

    const net::CacheType cache_type_;
    uint64_t cache_size_; // Total cache storage size in bytes.
    uint64_t max_size_;
    uint64_t high_watermark_;
    uint64_t low_watermark_;
    bool eviction_in_progress_;
    base::TimeTicks eviction_start_time_;

    // This stores all the entry_hash of entries that are removed during
    // initialization.
    std::unordered_set<uint64_t> removed_entries_;
    bool initialized_;
    IndexInitMethod init_method_;

    std::unique_ptr<SimpleIndexFile> index_file_;

    scoped_refptr<base::SingleThreadTaskRunner> io_thread_;

    // All nonstatic SimpleEntryImpl methods should always be called on the IO
    // thread, in all cases. |io_thread_checker_| documents and enforces this.
    base::ThreadChecker io_thread_checker_;

    // Timestamp of the last time we wrote the index to disk.
    // PostponeWritingToDisk() may give up postponing and allow the write if it
    // has been a while since last time we wrote.
    base::TimeTicks last_write_to_disk_;

    base::OneShotTimer write_to_disk_timer_;
    base::Closure write_to_disk_cb_;

    typedef std::list<net::CompletionCallback> CallbackList;
    CallbackList to_run_when_initialized_;

    // Set to true when the app is on the background. When the app is in the
    // background we can write the index much more frequently, to insure fresh
    // index on next startup.
    bool app_on_background_;
};

} // namespace disk_cache

#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
